package com.zm.blog.aop;

import com.zm.blog.constants.CommonConst;
import com.zm.blog.entity.User;
import com.zm.blog.enums.BlogEnum;
import com.zm.blog.enums.CodeMsg;
import com.zm.blog.handle.BlogLoginException;
import com.zm.blog.handle.BlogRuntimeException;
import com.zm.blog.utils.BlogUtil;
import com.zm.blog.utils.cache.BlogCache;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * @Author: Sister Xiao Ming
 * @Description: 切面类 `LoginCheckAspect`，它用于处理带有 `@LoginCheck` 注解的方法，实现登录状态和权限的校验
 * 主要步骤包括：
 *  1. 获取请求中的token
 *  2. 根据token从缓存中获取用户信息
 *  3. 校验用户是否存在（登录是否过期）
 *  4. 根据token类型（用户token或管理员token）和注解要求的权限等级进行权限校验
 *  5. 重置token的过期时间（实现续期功能）
 */
@Aspect
@Slf4j
@Component
@Order(0)    //最高优先级，最先执行
public class LoginCheckAspect {

    @Around("@annotation(loginCheck)")
    public Object checkLogin(ProceedingJoinPoint joinPoint, LoginCheck loginCheck) throws Throwable {
        //1.获取并校验 Token
        String token = BlogUtil.getToken();
        if (!StringUtils.hasText(token)) {
            throw new BlogLoginException(CodeMsg.NOT_LOGIN.getMsg());
        }

        //2.获取用户信息并校验
        User user = getUserFromCache(token);
        validateUserType(token, loginCheck, user);
        validatePermission(loginCheck, user);

        //3.执行 Token 续期
        renewTokenExpiration(token, user);

        //4.执行目标方法
        return joinPoint.proceed();

    }

    /**
     * 从缓存获取用户信息
     * @param token
     * @return
     */
    private User getUserFromCache(String token) {
        User user = (User) BlogCache.get(token);
        if (user == null) {
            throw new BlogLoginException(CodeMsg.LOGIN_EXPIRED.getMsg());
        }
        return user;
    }

    /**
     * 校验用户类型匹配
     *
     * @param token
     * @param loginCheck
     * @param user
     */
    private void validateUserType(String token, LoginCheck loginCheck, User user) {
        //普通用户 Token 访问管理员接口
        if (token.contains(CommonConst.USER_ACCESS_TOKEN)) {
            if (loginCheck.value() == BlogEnum.USER_TYPE_ADMIN.getCode() ||
                    loginCheck.value() == BlogEnum.USER_TYPE_DEV.getCode()) {
                throw new BlogRuntimeException("请输入管理员账号！");
            }
        }
        //管理员 Token 访问管理员接口
        else if (token.contains(CommonConst.ADMIN_ACCESS_TOKEN)) {
            log.info("请求IP： {}", BlogUtil.getIpAddr(BlogUtil.getRequest()));
            //管理员接口但非超级管理员
            if (loginCheck.value() == BlogEnum.USER_TYPE_ADMIN.getCode() &&
                !user.getId().equals(CommonConst.ADMIN_USER_ID)) {
                throw new BlogRuntimeException("请输入管理员账号！");
            }
        }
        //无效 Token 类型
        else {
            throw new BlogLoginException(CodeMsg.NOT_LOGIN.getMsg());
        }
    }

    /**
     * 校验用户权限
     * @param loginCheck
     * @param user
     */
    private void validatePermission(LoginCheck loginCheck, User user) {
        if (loginCheck.value() < user.getUserType()) {
            throw new BlogRuntimeException("权限不足！");
        }
    }

    /**
     * 续期 Token 有效期
     * @param token
     * @param user
     */
    private void renewTokenExpiration(String token, User user) {
        String userId = user.getId().toString();
        String tokenType = token.contains(CommonConst.USER_ACCESS_TOKEN) ? "USER" : "ADMIN";

        //检查续期间隔 Key 是否存在
        String intervalKey = getIntervalKey(tokenType, userId);
        if (BlogCache.get(intervalKey) != null) {
            return; //未到续期间隔时间
        }

        //加锁防止并发续期
        synchronized (userId.intern()) {
            if (BlogCache.get(intervalKey) != null) {
                return;  //双重检查
            }

            //更新 Token 有效期
            BlogCache.put(token, user, CommonConst.TOKEN_EXPIRE);

            //设置续期间隔标记
            BlogCache.put(intervalKey, token, CommonConst.TOKEN_INTERVAL);

            //更新用户 Token 映射
            String tokenKey = getTokenKey(tokenType, userId);
            BlogCache.put(tokenKey, token, CommonConst.TOKEN_EXPIRE);
        }
    }

    /**
     *  获取 Token 续期间隔 Key
     * @param tokenType
     * @param userId
     * @return
     */
    private String getIntervalKey(String tokenType, String userId) {
        return tokenType.equals("USER") ? CommonConst.USER_TOKEN_INTERVAL + userId
                : CommonConst.ADMIN_TOKEN_INTERVAL + userId;
    }

    /**
     * 获取用户 Token 映射 Key
     * @param tokenType
     * @param userId
     * @return
     */
    private String getTokenKey(String tokenType, String userId) {
        return tokenType.equals("USER")
                ? CommonConst.USER_TOKEN + userId
                : CommonConst.ADMIN_TOKEN + userId;
    }


}
